﻿using System.Collections.Generic;
using Godot.core;
using Godot.core.math;
using Godot.scene.resource;
using Godot.scene.s3d;

namespace Godot.scene.main
{
    public partial class Viewport : Node
    {
        public void set_size_override(bool p_enable, Vector2? p_in_size = null, Vector2?  p_in_margin = null)
        {
            Vector2 p_size = p_in_size ?? new Vector2(-1, -1);
            Vector2 p_margin = p_in_margin ?? new Vector2();
        }

        public void set_size_override_stretch(bool p)
        {
            throw new System.NotImplementedException();
        }

        public World find_world()
        {
	        if (__world != null)
		        return __world;
	        else if (__parent != null)
		        return __parent.find_world();
	        else
		        return new World();
        }

        public void set_world(World p_world)
        {
            if (__world == p_world)
		        return;

	        if (is_inside_scene())
		        _propagate_exit_world(this);

    
	        if (find_world() != null && __camera != null)
		        __camera.notification(Camera.NOTIFICATION_LOST_CURRENT);

	        __world = p_world;

	        if (is_inside_scene())
		        _propagate_enter_world(this);

        #if !_3D_DISABLED
	        if (find_world() != null && __camera != null)
		        __camera.notification(Camera.NOTIFICATION_BECAME_CURRENT);
        #endif

	        //propagate exit

	        if (is_inside_scene()) 
            {
		        //VisualServer::get_singleton()->viewport_set_scenario(viewport,find_world()->get_scenario());
	        }

	        _update_listener();
        }

        protected void _update_listener() 
        {
            //if (is_inside_scene() && audio_listener && camera) {
            //    SpatialSoundServer::get_singleton()->listener_set_space(listener,find_world()->get_sound_space());
            //} else {
            //    SpatialSoundServer::get_singleton()->listener_set_space(listener,RID());
            //}
        }

        public void set_as_audio_listener(bool p_enable) 
        {
            //throw new System.NotImplementedException();
        }

        public void set_as_audio_listener_2d(bool p_enable)
        {
            ///throw new System.NotImplementedException();
        }

        public void set_rect(Rect2 p_rect)
        {
            if (__rect == p_rect)
                return;
            __rect = p_rect;
            _update_rect();
            _update_stretch_transform();
        }

        protected void _update_rect()
        {
            if (!is_inside_scene())
		        return;

	        Node parent = get_parent();

            //if (!render_target && parent != null && parent->cast_to<Control>()) {

            //    Control *c = parent->cast_to<Control>();

            //    rect.pos=Point2();
            //    rect.size=c->get_size();
            //}

            //VisualServer::ViewportRect vr;
            //vr.x=rect.pos.x;
            //vr.y=rect.pos.y;
            //if (render_target) {
            //    vr.x=0;
            //    vr.y=0;
            //}
            //vr.width=rect.size.width;
            //vr.height=rect.size.height;
            //VisualServer::get_singleton()->viewport_set_rect(viewport,vr);

            //if (canvas_item.is_valid()) {
            //    VisualServer::get_singleton()->canvas_item_set_custom_rect(canvas_item,true,rect);
            //}

	        emit_signal("size_changed");
        }

        protected void _update_stretch_transform()
        {

        }

        protected void _propagate_exit_world(Node p_node) 
        {
	        if (p_node != this) 
            {
		        if (!p_node.is_inside_scene()) //may have exited scene already
			        return;

		        Spatial s = p_node as Spatial;
		        if (s != null) 
                {
			        s.notification(Spatial.NOTIFICATION_EXIT_WORLD, false);
		        } 
                else 
                {
			        Viewport v = p_node as Viewport;
			        if (v != null) 
                    {

				        if (v.__world != null)
					        return;
			        }
		        }
	        }


	        for(int i = 0; i < p_node.get_child_count(); i++) 
            {
		        _propagate_exit_world(p_node.get_child(i));
	        }

        }

        protected void _propagate_enter_world(Node p_node) 
        {
	        if (p_node != this) 
            {
		        if (!p_node.is_inside_scene()) //may not have entered scene yet
			        return;

		        Spatial s = p_node as Spatial;
		        if (s != null) 
                {
			        s.notification(Spatial.NOTIFICATION_ENTER_WORLD);
		        } 
                else 
                {
			        Viewport v = p_node as Viewport;
			        if (v != null) 
                    {
				        if (v.__world != null)
					        return;
			        }
		        }
	        }


	        for(int i = 0; i < p_node.get_child_count(); i++) 
            {
		        _propagate_enter_world(p_node.get_child(i));
	        }
        }

        public Viewport()
        {
            //world_2d = Ref<World2D>( memnew( World2D ));

            //viewport = VisualServer::get_singleton()->viewport_create();
            //listener=SpatialSoundServer::get_singleton()->listener_create();
            //audio_listener=false;
            //listener_2d=SpatialSound2DServer::get_singleton()->listener_create();
            //audio_listener_2d=false;
            //transparent_bg=false;
            //parent=NULL;
            //camera=NULL;
            //size_override=false;
            //size_override_stretch=false;
            //size_override_size=Size2(1,1);
            //render_target=false;
            //render_target_update_mode=RENDER_TARGET_UPDATE_WHEN_VISIBLE;
            //render_target_texture = Ref<RenderTargetTexture>( memnew( RenderTargetTexture(this) ) );
            __rect = new Rect2();
        }

        private Viewport __parent;
        private Camera __camera;
        private World __world;
        private Rect2 __rect;
    }

    public partial class Viewport
    {
        #region OBJ_TYPE( Viewport, Node )
        /* TYPE API */
        public override string get_type() { return "Viewport"; }

        public override string get_save_type() { return get_type(); } //type stored when saving

        public override StringName get_type_name() { return (StringName)get_type_static(); }

        public override bool is_type(string p_type) { return (p_type == "Viewport") ? true : base.is_type(p_type); }

        protected override void _initialize_typev() { initialize_type(); }

        protected override bool _setv(StringName p_name, Variant p_property)
        {
            if (base._setv(p_name, p_property))
                return true;

            if (typeof(Viewport).FindMethod("_set") != typeof(Node).FindMethod("_set"))
            {
                return _set(p_name, p_property);
            }
            return false;
        }

        protected override bool _getv(StringName p_name, ref Variant r_property)
        {
            if (typeof(Viewport).FindMethod("_get") != typeof(Node).FindMethod("_get"))
            {
                if (_get(p_name, ref r_property))
                    return true;
            }

            return base._getv(p_name, ref r_property);
        }

        protected override void _get_property_listv(ref List<PropertyInfo> p_list, bool p_reversed)
        {
            if (!p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }

            p_list.Add(new PropertyInfo(Variant.Type.NIL, get_type_static(), PropertyHint.PROPERTY_HINT_NONE, "", (int)PropertyUsageFlags.PROPERTY_USAGE_CATEGORY));

            if (!_is_gpl_reversed())
                ObjectTypeDB.get_property_list("Viewport", ref p_list, true);

            if (typeof(Viewport).FindMethod("_get_property_list") != typeof(Node).FindMethod("_get_property_list"))
            {
                _get_property_list(ref p_list);
            }

            if (_is_gpl_reversed())
                ObjectTypeDB.get_property_list("Viewport", ref p_list, true);

            if (p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }
        }

        protected override void _notificationv(int p_notification, bool p_reversed)
        {
            if (!p_reversed)
                base._notificationv(p_notification, p_reversed);

            if (typeof(Viewport).FindMethod("_notification") != typeof(Node).FindMethod("_notification"))
            {
                _notification(p_notification);
            }

            if (p_reversed)
                base._notificationv(p_notification, p_reversed);
        }

        public static new string get_type_static() { return "Viewport"; }

        public static new string get_parent_type_static() { return Node.get_type_static(); }

        public static new void get_inheritance_list_static(ref List<string> r_inheritance_list)
        {
            Node.get_inheritance_list_static(ref r_inheritance_list);
            r_inheritance_list.Add(get_type_static());
        }

        public static new string get_category_static()
        {
            string category = Node.get_category_static();
            if (typeof(Viewport).FindMethod("_get_category") != typeof(Node).FindMethod("_get_category"))
            {
                if (category != "")
                    category += "/";
                category += _get_category();
            }
            return category;
        }

        public static new string inherits_static() { return "Node"; }

        public static new void get_valid_parents_static(ref List<string> p_parents)
        {
            if (typeof(Viewport).FindMethod("_get_valid_parents_static") != typeof(Node).FindMethod("_get_valid_parents_static"))
            {
                Viewport._get_valid_parents_static(ref p_parents);
            }

            Node.get_valid_parents_static(ref p_parents);
        }

        public static new void initialize_type()
        {
            if (__initialized)
                return;
            Node.initialize_type();
            ObjectTypeDB._add_type<Viewport>();
            if (typeof(Viewport).FindMethod("_bind_methods") != typeof(Node).FindMethod("_bind_methods"))
                _bind_methods();
            __initialized = true;

        }

        private static bool __initialized = false;
        #endregion
    }
}
